﻿namespace VisualPhysics.Scene.Core

open System.Drawing
open VisualPhysics.Scene.Core.Operators

/// The BoxObject type represents a Box.
type BoxObject private (
                        position: MVector<cm>, width: float<cm>, height: float<cm>, mass: float<gram>, rotation: float,
                        color: Brush, velocity: MVector<cm / s>, lastUpdate: float<s>, dummy: unit) =
    let carPosition = car position
    
    /// <summary>Creates a new box with the given parameters.</summary>
    /// <param name="Position">The position in centimeters.</param>
    /// <param name="Width">The width in centimeters.</param>
    /// <param name="Height">The height in centimeters.</param>
    /// <param name="Mass">The mass in gram.</param>
    /// <param name="Rotation">The rotation angle.</param>
    /// <param name="Color">The color as a System.Drawing.Brush.</param>
    /// <param name="Velocity">The velocity of the box.</param>
    /// <param name="LastUpdate">The time of the last update.</param>
    new (position, width, height, mass, ?rotation, ?color, ?velocity, ?lastUpdate) =
        let rotation = defaultArg rotation 0.0
        let color = defaultArg color Brushes.Black
        let velocity = defaultArg velocity (0.0<cm / s>, 0.0)
        let lastUpdate = defaultArg lastUpdate 0.0<s>
        BoxObject(position, width, height, mass, rotation, color, velocity, lastUpdate, ())

    /// <summary>Creates a new static box with the given parameters.</summary>
    /// <param name="Position">The position in centimeters.</param>
    /// <param name="Width">The width in centimeters.</param>
    /// <param name="Height">The height in centimeters.</param>
    /// <param name="Rotation">The rotation angle.</param>
    /// <param name="Color">The color as a System.Drawing.Brush.</param>
    /// <param name="Velocity">The velocity of the box.</param>
    /// <param name="LastUpdate">The time of the last update.</param>
    /// <returns>A static box.</returns>
    static member Static (position, width, height, ?rotation, ?color, ?velocity, ?lastUpdate) =
        let rotation = defaultArg rotation 0.0
        let color = defaultArg color Brushes.Black
        let velocity = defaultArg velocity (0.0<cm / s>, 0.0)
        let lastUpdate = defaultArg lastUpdate 0.0<s>
        BoxObject(position, width, height, System.Double.PositiveInfinity * 1.0<gram>, rotation, color, velocity, lastUpdate, ())

    /// A list of the 4 points (tuples in centimeters) which represent the rectangle which the user sees.
    member this.Points =
            let topleft = carPosition
            let topright = fst carPosition + width * cos rotation, snd carPosition - width * sin rotation
            let bottomleft = fst carPosition + height * sin rotation, snd carPosition + height * cos rotation
            let bottomright = fst topright + fst bottomleft - fst carPosition, snd topright + snd bottomleft - snd carPosition
            [topleft;topright;bottomleft;bottomright]

    interface Object with
        /// The tag of the box - Box
        member this.Tag =
            if System.Double.IsPositiveInfinity(mass / 1.0<gram>) then
                StaticBox
            else
                Box
        /// The time of the last update.
        member this.LastSync = lastUpdate
        /// The position of the box as a tuple in centimeters.
        member this.CarPosition = carPosition
        /// The position of the box as an MVector of cm centimeters.
        member this.PolPosition = position
        /// The position of the box on the X axis in centimeters.
        member this.X = fst carPosition
        /// The position of the box on the Y axis in centimeters.
        member this.Y = snd carPosition
        /// The current velocity of the box.
        member this.Velocity = velocity
        /// The color of the box (System.Drawing.Brush).
        member this.Color = color
        /// The mass of the box in gram.
        member this.Mass = mass
        /// <summary>Updates the box's position and velocity to a given time with given set of forces.</summary>
        /// <param name="ForceSet">The AccelerationManager which will be used to calculate new velocity and position.</param>
        /// <returns>The updated box.</returns>
        member this.Sync forceSet time =
            let dt = time - lastUpdate
            if dt <= 0.0<s> then
                this.Object()
            else
                let _velocity = forceSet.GetVelocity time velocity
                let _position = forceSet.GetPosition time velocity position
                BoxObject(_position, width, height, mass, rotation, color, _velocity, time).Object()
        /// <summary>Returns a new box with the new position.</summary>
        /// <param name="NewPosition">The new position as a tuple in centimeters.</param>
        /// <returns>The new box as Object.</returns>
        member this.SetCarPosition _position =
            let _position = pol _position
            BoxObject(_position, width, height, mass, rotation, color, velocity, lastUpdate).Object()
        /// <summary>Returns a new box with the new position.</summary>
        /// <param name="NewPosition">The new position as an MVector of centimeters.</param>
        /// <returns>The new box as Object.</returns>
        member this.SetPolPosition _position =
            BoxObject(_position, width, height, mass, rotation, color, velocity, lastUpdate).Object()
        /// <summary>Returns a new box with the new color.</summary>
        /// <param name="NewColor">The new color.</param>
        /// <returns>The new box as Object.</returns>
        member this.SetColor _color =
            BoxObject(position, width, height, mass, rotation, _color, velocity, lastUpdate).Object()
        /// <summary>Draws the box using a given System.Drawing.Graphics.</summary>
        /// <param name="Graphics">The graphics which will be used to draw.</param>
        member this.Draw (g : Graphics) =
            g.FillClosedCurve(color, Array.ofList <| (List.map point <| this.Points))
    end

    member this.Rotation = rotation
    // The size of the box as a tuple in centimeters.
    member this.Size = (width, height)
    /// <summary>Returns a new box with the new rotation angle.</summary>
    /// <param name="NewColor">The new angle in radians.</param>
    /// <returns>The new box.</returns>
    member this.SetRotation _rotation =
        BoxObject(position, width, height, mass, _rotation, color, velocity, lastUpdate)

    /// Up-casted to an Object.
    member this.Object() = this :> Object